﻿//
// util.hpp
// ~~~~~~~~
//

#ifndef YUTOPP_OAUTH_UTILITY_HPP
#define YUTOPP_OAUTH_UTILITY_HPP

#include "encoding.hpp"

#include <string>
#include <locale>
#include <utility>
#include <exception>

#include <boost/format.hpp>
#include <boost/foreach.hpp>

#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <openssl/buffer.h>

namespace webapp
{
	//
	namespace url
	{
		/*
		  参考元：http://d.hatena.ne.jp/ytakano/20081016/urlencode
		*/
		std::string encode( const std::string& str )
		{
			std::string st;

			BOOST_FOREACH( const char c, encoding::u8( str ) ) {
				if ( ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) ||
					 ( c >= '0' && c <= '9' ) ||
					   c == '-' || c == '_' || c == '.' || c == '~' )
				{
					st += c;
				} else {
					st += ( boost::format( "%%%1$02X" ) % ( c & 0xff ) ).str();
				}
			}
	
			return st;
		}

		std::pair<std::string, std::string> encode_pair( const std::pair<std::string, std::string>& p )
		{
			return std::make_pair( encode( p.first ), encode( p.second ) );
		}

		/*
		*/
		std::string decode( const std::string& str )
		{
			static const std::string table( "0123456789ABCDEF" );
			std::locale loc;
			std::string st;
			
			const auto last = str.cend();
			for( auto it = str.cbegin(); it < last; ++it ) {
				if ( (*it) == '%' ) {
					if ( ( it + 3 ) > last )
						break;

					char code = table.find( std::toupper( *++it, loc ) ) * 16;
					code += table.find( std::toupper( *++it, loc ) );

					st += code;
				} else if ( (*it) == '+' ) {
					st += ' ';
				} else {
					st += *it;
				}
			}
			return st;
		}

		std::pair<std::string, std::string> decode_pair( const std::pair<std::string, std::string>& p )
		{
			return std::make_pair( decode( p.first ), decode( p.second ) );
		}
	} // - url

	//
	namespace base64
	{
		/*
		  参考元：http://gazpara.blogspot.com/2010/03/pythoncbase64.html
		*/
		std::string encode( const void* src_data, const size_t src_len )
		{
			BUF_MEM* bptr = nullptr;
	
			if( src_data == NULL || src_len <= 0 )
				throw std::exception( "failed at base64::encode incorrect data.", 0 );

			BIO* b64 = BIO_new( BIO_f_base64() );
			if ( !b64 )
				throw std::exception( "failed at base64::encode BIO_new BIO_f_base64.", 1 );
	
			BIO* mem = BIO_new( BIO_s_mem() );
			if ( !mem ) {
				BIO_vfree( b64 );
				throw std::exception( "failed at base64::encode BIO_new BIO_f_mem.", 2 );
			}
	
			BIO* chain = BIO_push( b64, mem );
	
			if ( BIO_write( chain, src_data, src_len ) <= 0 ) {
				BIO_free_all( chain );
				throw std::exception( "failed at base64::encode BIO_write.", 3 );
			}

			if( BIO_flush( chain ) <= 0 ) {
				BIO_free_all( chain );
				throw std::exception( "failed at base64::encode BIO_flush.", 4 );
			}
	
			BIO_get_mem_ptr( chain, &bptr );
			/*if ( src_len < bptr->length ) {
				BIO_free_all( chain );
				throw std::exception( "base64::failed to get pointer.", 4 );
			}*/

			const std::string ret( bptr->data, bptr->length - 1 );

			BIO_free_all( chain );

			return ret;
		}
	} // - base64
}

#endif /*YUTOPP_OAUTH_UTILITY_HPP*/
